﻿using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using GridCut.Scaffold;
using Scaffold;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GridCut
{
	public partial class Form1 : Form
	{
		public static PCIBoard pci;
		public static ImageProcessor ip;
		List<List<Control>> controls;
		BGSubtractor subtractor;
		MindVisionCamera camera;
		List<Point> msePath;
		Mat bximg, bxtmp, bxshow;
		List<Rectangle> rects;
		Mat CalibratedImg;
		Thread work;
		List<Point> bigboardVert, bigboardHori;

		public Form1()
		{
			InitializeComponent();
			Init();

		}
		public void Init()
		{
			msePath = new List<Point>();
			controls = new List<List<Control>>()
			{
				new List<Control> {tbwid1, tbhei1 },
				new List<Control> {tbwid2, tbhei2 },
				new List<Control> {tbwid3, tbhei3 },
				new List<Control> {tbwid4, tbhei4 },
				new List<Control> {tbwid5, tbhei5 },
				new List<Control> {tbwid6, tbhei6 },
				new List<Control> {tbwid7, tbhei7 },
				new List<Control> {tbwid8, tbhei8 },
			};
			tbwid1.DataBindings.Add(new Binding("Text", Config.config, "BigWid1", true));
			tbwid2.DataBindings.Add(new Binding("Text", Config.config, "BigWid2", true));
			tbwid3.DataBindings.Add(new Binding("Text", Config.config, "BigWid3", true));
			tbwid4.DataBindings.Add(new Binding("Text", Config.config, "BigWid4", true));
			tbwid5.DataBindings.Add(new Binding("Text", Config.config, "BigWid5", true));
			tbwid6.DataBindings.Add(new Binding("Text", Config.config, "BigWid6", true));
			tbwid7.DataBindings.Add(new Binding("Text", Config.config, "BigWid7", true));
			tbwid8.DataBindings.Add(new Binding("Text", Config.config, "BigWid8", true));

			tbhei1.DataBindings.Add(new Binding("Text", Config.config, "BigHei1", true));
			tbhei2.DataBindings.Add(new Binding("Text", Config.config, "BigHei2", true));
			tbhei3.DataBindings.Add(new Binding("Text", Config.config, "BigHei3", true));
			tbhei4.DataBindings.Add(new Binding("Text", Config.config, "BigHei4", true));
			tbhei5.DataBindings.Add(new Binding("Text", Config.config, "BigHei5", true));
			tbhei6.DataBindings.Add(new Binding("Text", Config.config, "BigHei6", true));
			tbhei7.DataBindings.Add(new Binding("Text", Config.config, "BigHei7", true));
			tbhei8.DataBindings.Add(new Binding("Text", Config.config, "BigHei8", true));

			//For Test:
			bximg = CvInvoke.Imread(@"baboon.jpg");
			bxtmp = new Mat();
			bxshow = new Mat();
			bximg.CopyTo(bxtmp);
			imageBox1.Image = bximg;
			rects = new List<Rectangle>();

			try
			{
				camera = new MindVisionCamera(0);
				camera.OnReceivingImage += On_ReceivingImage;
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
			}

			try
			{
				PCIBoard.GlobalInit();
				pci = new PCIBoard();
				for (ushort i = 0; i <= 5; i++)
					pci.Output(i, false);
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
			}
			try
			{
				ip = new ImageProcessor(Config.config.ChessWid, Config.config.ChessHei, @".\chess", @".\CameraConfig.yml");
				subtractor = new BGSubtractor(ip);
				subtractor.Fit(@".\train");
			}
			catch (Exception e)
			{
				Console.WriteLine("请在程序目录下建立train文件夹，用于保存原始背景；建立chess文件夹，存放数个棋盘格子图像，并设置4个参考点。然后重新打开程序。");
				Console.WriteLine(e.Message);
			}
		}

		private void ImageBox1_MouseMove(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				msePath.Add(Utils.Convert2ImagePt(e.Location, imageBox1));
				if (msePath.Count > 1)
				{
					CvInvoke.Line(bxtmp, msePath[msePath.Count - 2], msePath[msePath.Count - 1], new Emgu.CV.Structure.MCvScalar(255, 255, 0));
				}
				imageBox1.Image = bxtmp;
			}
		}

		private void ImageBox1_MouseUp(object sender, MouseEventArgs e)
		{
			Rectangle r = CvInvoke.BoundingRectangle(new VectorOfPoint(msePath.ToArray()));
			bxtmp.CopyTo(bxshow);
			CvInvoke.Rectangle(bxshow, r, new Emgu.CV.Structure.MCvScalar(255, 255, 0));
			imageBox1.Image = bxshow;
		}

		private void ImageBox1_MouseDown(object sender, MouseEventArgs e)
		{
			msePath.Add(Utils.Convert2ImagePt(e.Location, imageBox1));
		}

		public void On_ReceivingImage(object sender, ReceivingArgs e)
		{
			Console.WriteLine($"Receiving Image: {e.size}*{e.channels}");
			Mat NotCalibrated = new Mat(e.size, DepthType.Cv8U, e.channels, e.FrameBuffer, e.size.Width * e.channels);
			CvInvoke.Flip(NotCalibrated, NotCalibrated, FlipType.Vertical);


			CalibratedImg = ip.Transform(NotCalibrated);
			CvInvoke.Imwrite("./cap.jpg", CalibratedImg); Console.WriteLine("校正后的图片已保存至cap.jpg");

			Mat show = new Mat();
			VectorOfVectorOfPoint contours;
			Mat mask = new Mat(), fgimg = new Mat();

			subtractor.Extract(CalibratedImg, mask, fgimg);

			List<List<Point>> allpath = new List<List<Point>>();
			List<Point> path = new List<Point>();
			contours = ip.ExtractContour(mask);
			for (int i = 0; i < contours.Size; i++)
			{
				try
				{
					allpath.Add(ip.GenHoriCuttingPath(contours[i]));
				}
				catch { }
			}
			fgimg.CopyTo(show);
			if (allpath.Count > 0)
			{
				path = allpath.Aggregate((y, x) => { return x.Concat(y).ToList(); });
				for (int j = 1; j < path.Count; j++)
				{
					CvInvoke.ArrowedLine(show, path[j - 1], path[j], Utils.RandomColor());
				}
			}
			imageBox1.Image = show;

			if (MessageBox.Show(this, "使用自动模式切割，点击确定；使用手动模式，点击取消。", "是否进行自动切割", MessageBoxButtons.OKCancel) == DialogResult.Cancel)
			{
				Console.WriteLine("使用手动切割模式切割，鼠标绘图。");
				imageBox1.MouseDown += ImageBox1_MouseDown;
				imageBox1.MouseUp += ImageBox1_MouseUp;
				imageBox1.MouseMove += ImageBox1_MouseMove;
				btnNxt.Enabled = btnRedraw.Enabled = btnSubmit.Enabled = true;

				bximg = new Mat(); bxtmp = new Mat(); bxshow = new Mat();
				CalibratedImg.CopyTo(bximg);
				bximg.CopyTo(bxtmp);
				imageBox1.Image = bximg;
				rects = new List<Rectangle>();
				return;
			}

			work = new Thread(() =>
			{
				PCIBoard.Speed spspeed = new PCIBoard.Speed(Config.config.SPVMin, Config.config.SPVMax, Config.config.SPTacc);

				Console.WriteLine("使用自动切割模式切割.");
				Utils.ExecuteCut(pci, path, Config.config.Offset);
				pci.Output(4, true);            //旋转
				pci.Output(5, true);            //油泵开
				pci.WaitForInputState(4, true);
				pci.Output(4, false);           //停止旋转
				Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵

				mask = ip.Rotate90AntClk(mask);
				fgimg = ip.Rotate90AntClk(fgimg);

				contours = ip.ExtractContour(mask);
				path = ip.GenVertCuttingPath(contours, out int totalCount);
				fgimg.CopyTo(show);
				for (int j = 1; j < path.Count; j++)
				{
					CvInvoke.ArrowedLine(show, path[j - 1], path[j], Utils.RandomColor());
				}
				imageBox1.Image = show;

				Utils.ExecuteCut(pci, path, Config.config.OffsetAfterSpin);
				pci.Output(1, false);

				new Thread(() =>
				{
					pci.Output(0, true);          //旋转
					pci.Output(5, true);            //油泵开
					pci.WaitForInputState(0, true);
					pci.Output(0, false);         //停止旋转
					Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵
				}).Start();
				pci.MoveXY(Config.config.StartPoint, Point.Empty, spspeed, false);

			});
			work.Start();
		}

		private void btnSetting_Click(object sender, EventArgs e)
		{
			Config old = new Config(Config.config);
			DialogResult result = new Setting().ShowDialog();
			if (result != DialogResult.OK)
			{
				Config.config = new Config(old);
			}
		}

		private void btnStop_Click(object sender, EventArgs e)
		{
			if (camera != null)
				camera.Stop();
			if (work != null)
			{
				work.Abort();
				pci.Stop();
				pci.Output(1, false);      //转刀停止
				pci.Output(2, true);      //上升
				pci.WaitForInputState(2, true);
				pci.Output(2, false);     //停止上升
				for (ushort i = 0; i <= 5; i++)
					pci.Output(i, false);
			}
		}

		private void btnStart_Click(object sender, EventArgs e)
		{
			if (pci != null && camera != null)
			{
				if (pci.Read(2) != 0) return;
				PCIBoard.Speed spspeed = new PCIBoard.Speed(Config.config.SPVMin, Config.config.SPVMax, Config.config.SPTacc);
				pci.MoveXY(Config.config.StartPoint, Point.Empty, spspeed, false);
				camera.Play();
			}
		}

		private void btnNxt_Click(object sender, EventArgs e)
		{
			bxshow.CopyTo(bximg);
			bximg.CopyTo(bxtmp);
			rects.Add(CvInvoke.BoundingRectangle(new VectorOfPoint(msePath.ToArray())));
			msePath.Clear();
		}

		private void btnRedraw_Click(object sender, EventArgs e)
		{
			msePath.Clear();
			bximg.CopyTo(bxtmp);
			imageBox1.Image = bximg;
		}


		private void btnPreviewHori_Click(object sender, EventArgs e)
		{
			Mat toDrawPath = new Mat();

			List<Point> path = ip.GenHoriCuttingPath(rects);
			bximg.CopyTo(toDrawPath);
			for (int j = 1; j < path.Count; j++)
			{
				CvInvoke.ArrowedLine(toDrawPath, path[j - 1], path[j], Utils.RandomColor(), 4);
			}
			imageBox1.Image = toDrawPath;
		}

		private void btnPreviewVert_Click(object sender, EventArgs e)
		{
			Mat toDrawPath = new Mat();

			toDrawPath = ip.Rotate90AntClk(bximg);
			var path = ip.GenVertCuttingPath(rects, bximg.Size);
			for (int j = 1; j < path.Count; j++)
			{
				CvInvoke.ArrowedLine(toDrawPath, path[j - 1], path[j], Utils.RandomColor(), 4);
			}
			imageBox1.Image = toDrawPath;
		}

		private void btnHome_Click(object sender, EventArgs e)
		{
			PCIBoard.Speed hmspeed = new PCIBoard.Speed(Config.config.HMVMin, Config.config.HMVMax, Config.config.HMTacc);
			pci.MoveHome(hmspeed, Config.config.SlowSpeed, Config.config.SlowTime); pci.Stop();
		}

		private void btnManual_Click(object sender, EventArgs e)
		{
			new Manual().Show();
		}

		private void btnBigPrevHori_Click(object sender, EventArgs e)
		{
			int id = GetSelectIndex(comboBox1);
			int hei = (int)(double.Parse(GetText(controls[id][1])) * Config.config.PXpMM);
			bigboardHori = new List<Point>();
			var rect = rects[0];
			int L = rect.Left - Config.config.GetExtraXPX();
			int R = rect.Right + Config.config.GetExtraXPX();
			for (int H = rect.Top; H < rect.Bottom; H += hei+(int)(Config.config.KnfThk*Config.config.PXpMM))
			{
				bigboardHori.Add(new Point(L, H));
				bigboardHori.Add(new Point(R, H));
			}

			Mat toDrawPath = new Mat();
			bximg.CopyTo(toDrawPath);
			for (int j = 1; j < bigboardHori.Count; j++)
			{
				CvInvoke.ArrowedLine(toDrawPath, bigboardHori[j - 1], bigboardHori[j], Utils.RandomColor(), 4);
			}
			imageBox1.Image = toDrawPath;
		}

		private delegate object GT(Control c);
		private delegate int GT2(Control c);
		private string GetText(Control c)
		{
			if (c.InvokeRequired)
			{
				return (string)c.Invoke(new GT(GetText), c);
			}
			return c.Text;
		}
		private int GetSelectIndex(Control cb)
		{
			if (cb.InvokeRequired)
				return (int)cb.Invoke(new GT2(GetSelectIndex), cb);
			return (cb as ComboBox).SelectedIndex;
		}

		private void btnBigPrevVert_Click(object sender, EventArgs e)
		{
			int id = GetSelectIndex(comboBox1);
			int wid = (int)(double.Parse(GetText(controls[id][0])) * Config.config.PXpMM);
			bigboardVert = new List<Point>();

			var rect = new Rectangle(new Point(rects[0].Y, bximg.Width - rects[0].Right), new Size(rects[0].Height, rects[0].Width));
			int L = rect.Left - Config.config.GetExtraXPX();
			int R = rect.Right + Config.config.GetExtraXPX();
			for (int H = rect.Top; H < rect.Bottom; H += wid + (int)(Config.config.KnfThk * Config.config.PXpMM))
			{
				bigboardVert.Add(new Point(L, H));
				bigboardVert.Add(new Point(R, H));
			}


			Mat toDrawPath = new Mat();
			toDrawPath = ip.Rotate90AntClk(bximg);
			for (int j = 1; j < bigboardVert.Count; j++)
			{
				CvInvoke.ArrowedLine(toDrawPath, bigboardVert[j - 1], bigboardVert[j], Utils.RandomColor(), 4);
			}
			imageBox1.Image = toDrawPath;
		}

		private void btnBigBoardCut_Click(object sender, EventArgs e)
		{
			work = new Thread(() =>
			{
				PCIBoard.Speed spspeed = new PCIBoard.Speed(Config.config.SPVMin, Config.config.SPVMax, Config.config.SPTacc);
				btnBigPrevHori_Click(null, null);
				Utils.ExecuteCut(pci, bigboardHori, Config.config.Offset);
				pci.Output(4, true);            //旋转
				pci.Output(5, true);            //油泵开
				pci.WaitForInputState(4, true);
				pci.Output(4, false);           //停止旋转
				Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵

				btnBigPrevVert_Click(null, null);
				Utils.ExecuteCut(pci, bigboardVert, Config.config.OffsetAfterSpin);
				pci.Output(1, false);

				new Thread(() =>
				{
					pci.Output(0, true);          //旋转
					pci.Output(5, true);            //油泵开
					pci.WaitForInputState(0, true);
					pci.Output(0, false);         //停止旋转
					Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵
				}).Start();
				pci.MoveXY(Config.config.StartPoint, Point.Empty, spspeed, false);
			});
			work.Start();
		}

		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			Config.config.Save();
		}

		private void button1_Click(object sender, EventArgs e)
		{
			Mat mat = CvInvoke.Imread(@"C:\Users\Legend\Desktop\train\1.JPG");
			On_ReceivingImage(null, new ReceivingArgs(mat.Width, mat.Height,mat.NumberOfChannels, mat.DataPointer));
		}

		private void btnSubmit_Click(object sender, EventArgs e)
		{
			imageBox1.MouseDown -= ImageBox1_MouseDown;
			imageBox1.MouseUp -= ImageBox1_MouseUp;
			imageBox1.MouseMove -= ImageBox1_MouseMove;
			btnNxt.Enabled = btnRedraw.Enabled = btnSubmit.Enabled = false;


			work = new Thread(() =>
			{
				PCIBoard.Speed spspeed = new PCIBoard.Speed(Config.config.SPVMin, Config.config.SPVMax, Config.config.SPTacc);
				btnPreviewHori_Click(null, null);
				List<Point> path = ip.GenHoriCuttingPath(rects);
				Utils.ExecuteCut(pci, path, Config.config.Offset);
				pci.Output(4, true);            //旋转
				pci.Output(5, true);			//油泵开
				pci.WaitForInputState(4, true);
				pci.Output(4, false);           //停止旋转
				Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵

				btnPreviewVert_Click(null, null);
				path = ip.GenVertCuttingPath(rects, bximg.Size);
				Utils.ExecuteCut(pci, path, Config.config.OffsetAfterSpin);
				pci.Output(1, false);

				new Thread(() =>
				{
					pci.Output(0, true);          //旋转
					pci.Output(5, true);            //油泵开
					pci.WaitForInputState(0, true);
					pci.Output(0, false);         //停止旋转
					Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵
				}).Start();
				pci.MoveXY(Config.config.StartPoint, Point.Empty, spspeed, false);
			});
			work.Start();
		}

	}
	public static class Utils
	{
		static Random rand = new Random();
		public static Emgu.CV.Structure.MCvScalar RandomColor()
		{
			return new Emgu.CV.Structure.MCvScalar(rand.Next(255), rand.Next(255), rand.Next(255));
		}

		public static Point Convert2ImagePt(Point mseLoc, Emgu.CV.UI.ImageBox ib)
		{
			//todo: case ib.sizemode, ib.scrollbar.value, ib.zoomscale

			int X = (int)((float)mseLoc.X / ib.Size.Width * ib.Image.Size.Width);
			int Y = (int)((float)mseLoc.Y / ib.Size.Height * ib.Image.Size.Height);
			return new Point(X, Y);
		}

		public static void ExecuteCut(PCIBoard pci, List<Point> path, Point offset)
		{
			PCIBoard.Speed espeed = new PCIBoard.Speed(Config.config.EVMin, Config.config.EVMax, Config.config.ETacc);
			PCIBoard.Speed ctspeed = new PCIBoard.Speed(Config.config.CTVMin, Config.config.CTVMax, Config.config.CTTacc);
			if (path.Count == 0) return;
			//消除齿轮间隙
			pci.MoveXY(path[0], offset, espeed, false);
			pci.MoveXY(new Point(path[0].X, path[0].Y - (int)(Config.config.PXpMM * Config.config.ExtraY)), offset, espeed, false);
			pci.MoveXY(path[0], offset, espeed, false);

			pci.Output(1, true);      //转刀
			for (int i = 0; i < path.Count; i += 2)
			{
				pci.Output(3, true);
				Thread.Sleep(Config.config.TotalTime - Config.config.CutCount * Config.config.TimePerCut);
				pci.Output(3, false);
				for (int j = 1; j <= Config.config.CutCount; j++)
				{
					pci.Output(3, true);	//下降
					pci.Output(5, true);	//油泵开
					pci.WaitForInputState(3, true, (j < Config.config.CutCount)? Config.config.TimePerCut:-1);
					pci.Output(3, false);   //停止下降
					Thread.Sleep(1000);	pci.Output(5, false);	//延时1秒关闭油泵


					if (j % 2 == 1)
						pci.MoveXY(path[i + 1], offset, ctspeed, false);
					else
						pci.MoveXY(path[i], offset, ctspeed, false);
				}

				pci.Output(2, true);      //上升
				pci.Output(5, true);	//油泵开
				pci.WaitForInputState(2, true);
				pci.Output(2, false);     //停止上升
				Thread.Sleep(1000); pci.Output(5, false);   //延时1秒关闭油泵

				if (i + 2 < path.Count)
					pci.MoveXY(path[i + 2], offset, espeed, false);	//空刀
			}
		}

	}

}
